package com.quark.framework.database;

import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.DriverManager;
import java.util.List;
import java.util.ArrayList;

import org.gjt.mm.mysql.Driver;

import com.quark.framework.configuration.QuarkConfigManager;
import com.quark.framework.utility.QuarkLog;

public class QuarkDBConnectionPool {
	
	private static QuarkDBConnectionPool inst = new QuarkDBConnectionPool();
	private QuarkDBConfiguration dbConfig;
	private int curConnectCount = 0;
	private List<QuarkDBPooledConnection> usedConnection = new ArrayList<QuarkDBPooledConnection>();
	private List<QuarkDBPooledConnection> freeConnection = new ArrayList<QuarkDBPooledConnection>();
	
	private QuarkDBConnectionPool()
	{
		
	}
	
	public void showConnectionStatus()
	{
		QuarkLog.log("FREE CONNECTION:" + freeConnection.size());
		QuarkLog.log("USED CONNECTION:" + usedConnection.size());
		for(int i = 0; i < usedConnection.size(); i ++)
		{
			QuarkLog.log("CONNECTION" + i + ": " + usedConnection.get(i).getRefCount());
		}
	}
	public static QuarkDBConnectionPool getInstance()
	{
		return inst;
	}
	
	public DatabaseMetaData getMetaData()
	{
		QuarkDBPooledConnection conn = getBestConnection();
		DatabaseMetaData data = null; 
		try
		{
			data = conn.getConnection().getMetaData();
		}
		catch(Exception e)
		{
			QuarkLog.log(e);			
		}finally
		{
			returnConnection(conn);
		}
		return data;
	}
	
	public boolean closeAllConnection()
	{
		try
		{
			for(int i = 0; i < usedConnection.size(); i++)
			{
				QuarkDBPooledConnection conn = usedConnection.get(i);
				while(conn.isUsing())
				{
					wait(50);
				}
				conn.getConnection().close();
			}
			
			for(int i = 0; i < freeConnection.size(); i++)
			{
				QuarkDBPooledConnection conn = freeConnection.get(i);
				while(conn.isUsing())
				{
					wait(50);
				}
				conn.getConnection().close();
			}
			usedConnection.clear();
			freeConnection.clear();
			return true;
		}
		catch(Exception e)
		{
			QuarkLog.log(e);
		}
		return false;
	}
	
	private Connection newConnection()
	{
		try
		{
			DriverManager.registerDriver(new Driver());
			Connection c = DriverManager.getConnection(dbConfig.createConnectionUrl(), dbConfig.dbUserName, dbConfig.dbUserPass);
			return c;
		}catch(Exception e)
		{
			QuarkLog.log(e);
			return null;
		}
		
	}
	
	public synchronized QuarkDBPooledConnection getBestConnection()
	{
		//try to get a connection from freelist first
		QuarkDBPooledConnection best = null;
		if(freeConnection.size() > 0)
		{
			QuarkDBPooledConnection c = freeConnection.get(0);
			c.addRef();
			freeConnection.remove(c);
			heapAddUsedConnection(c);
			best = c;
		}
		else if(usedConnection.size() > 0)
		{
			QuarkDBPooledConnection c = usedConnection.get(0);
			c.addRef();
			heapSortUsedConnection(0);
			if(c.getRefCount() > dbConfig.dbFullThredhold)
				createConnections();
			best = c;
		}
		else
		{
			QuarkLog.logToFile(new Exception("!!!TOO MUCH USERS,NOT ENOUGH CONNECTION!!!"));			
		}
		if(best != null)
		{
			if(!best.checkConnection())
			{
				returnConnection(best);
				checkConnections();
				return getBestConnection();
			}
		}
		else
		{
			checkConnections();
			return getBestConnection();
		}
		return best;
	}
	
	public synchronized void returnConnection(QuarkDBPooledConnection c)
	{
		if(c == null)
			return;
		for(int i = 0; i < usedConnection.size(); i++)
		{
			if(usedConnection.get(i).equals(c))
			{
				QuarkDBPooledConnection conn = usedConnection.get(i);
				conn.decRef();
				if(!conn.isUsing())
				{
					usedConnection.remove(i);
					freeConnection.add(conn);
				}
				else
				{
					heapSortUsedConnection(i);					
				}
			}
		}
	}
	
	public synchronized void returnConnection(Connection c)
	{
		if(c == null)
			return;
		for(int i = 0; i < usedConnection.size(); i++)
		{
			if(usedConnection.get(i).isSameConnection(c))
			{
				QuarkDBPooledConnection conn = usedConnection.get(i);
				conn.decRef();
				if(!conn.isUsing())
				{
					usedConnection.remove(i);
					freeConnection.add(conn);
				}
				else
				{
					heapSortUsedConnection(i);					
				}
			}
		}
	}	
	private synchronized int heapSortUsedConnection(int index)
	{
		QuarkDBPooledConnection conn = usedConnection.get(index);
		QuarkDBPooledConnection connPre = index > 0 ? usedConnection.get(index - 1) : null;
		QuarkDBPooledConnection connPost = index < usedConnection.size() - 1 ? usedConnection.get(index + 1) : null;
		
		if(connPost != null && connPost.getRefCount() < conn.getRefCount())
		{
			usedConnection.remove(index);
			for(int i = index + 1; i < usedConnection.size(); i++)
			{
				QuarkDBPooledConnection c = usedConnection.get(i);
				if(c.getRefCount() >= conn.getRefCount())
				{
					usedConnection.add(i, conn);
					return i;
				}
			}
			//add at last
			usedConnection.add(conn);
			return usedConnection.size() - 1;
		}
		else if(connPre != null && connPre.getRefCount() > conn.getRefCount())
		{
			usedConnection.remove(index);
			for(int i = index - 1; i >= 0; i--)
			{
				QuarkDBPooledConnection c = usedConnection.get(i);
				if(c.getRefCount() <= conn.getRefCount())
				{
					usedConnection.add(i, conn);
					return i;
				}
			}
			//add at front
			usedConnection.add(0, conn);
			return usedConnection.size() - 1;
		}
		//else sort not needed
		return index;
	}
	
	private synchronized int heapAddUsedConnection(QuarkDBPooledConnection conn)
	{
		int index = 0;
		if(usedConnection.size() == 0)
		{
			usedConnection.add(conn);
		}
		else
		{
			for(int i = 0 ; i < usedConnection.size(); i++)
			{
				if(usedConnection.get(i).getRefCount() <= conn.getRefCount())
				{
					usedConnection.add(i, conn);
					index = i;
					break;
				}
			}
		}
		return index;
	}
	
	public boolean createConnections()
	{
		if(curConnectCount >= dbConfig.dbMaxConnection)
		{
			QuarkLog.logToFile(new Exception("TOO MUCH USER,MAX CONNECTION COUNT REACHED!"));
			return false;
		}
		QuarkLog.log("QuarkDBConnectionPool: Creating connections...");
		for(int i = 0; i < dbConfig.dbIncrementCount && curConnectCount < dbConfig.dbMaxConnection; i ++)
		{
			try
			{
				Connection c = newConnection();
				freeConnection.add(new QuarkDBPooledConnection(c));
				curConnectCount ++;
			}
			catch(Exception e)
			{
				QuarkLog.log(e);
			}
		}
		QuarkLog.log("QuarkDBConnectionPool: Done");
		return true;
	}
	
	public boolean checkConnections()
	{
		try
		{
			for(int i = 0; i < usedConnection.size(); i++)
			{
				QuarkDBPooledConnection conn = usedConnection.get(i);
				if(!conn.checkConnection())
				{
					if(conn.getConnection().isClosed())
					{
						try{
						conn.getConnection().close();
						}
						catch(Exception e)
						{
							QuarkLog.log(e);
						}
						conn.setConnection(newConnection());
					}
				}
			}
			
			for(int i = 0; i < freeConnection.size(); i++)
			{
				QuarkDBPooledConnection conn = freeConnection.get(i);
				if(!conn.checkConnection())
				{
					if(conn.getConnection().isClosed())
					{
						try{
						conn.getConnection().close();
						}
						catch(Exception e)
						{
							QuarkLog.log(e);
						}
						conn.setConnection(newConnection());
					}
				}

			}
			return true;
		}
		catch(Exception e)
		{
			QuarkLog.log(e);
		}
		return false;
		
	}
	
	public boolean uninit()
	{
		closeAllConnection();
		return true;
	}
	
	public boolean init()
	{
		closeAllConnection();
		dbConfig = (QuarkDBConfiguration)QuarkConfigManager.getInstance().getConfiguration(QuarkDBConfiguration.class);//FIXME:USE CONFIG MANAGER
		createConnections();
		return true;
	}
}
